/****************************************************************************
 * Copyright (C) 2009-2010 SciTouch LLC
 * 
 * This file is part of Indigo toolkit.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.GPL included in the
 * packaging of this file.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

#include <ctype.h>

#include "base_cpp/scanner.h"
#include "base_cpp/output.h"

#include "molecule/elements.h"
#include "molecule/molecule.h"
#include "molecule/gross_formula.h"

int GrossFormula::_cmp (const _ElemCounter &ec1, const _ElemCounter &ec2, const void *context)
{
   if (ec1.counter == 0)
      return 1;
   if (ec2.counter == 0)
      return -1;

   if (ec2.elem == ELEM_H) // move hydrogen to the end
      return -1;
   if (ec1.elem == ELEM_H)
      return 1;

   return ec1.elem - ec2.elem;
}

// comparator implementing the Hill system
int GrossFormula::_cmp_hill (const _ElemCounter &ec1, const _ElemCounter &ec2, const void *context)
{
   if (ec1.counter == 0)
      return 1;
   if (ec2.counter == 0)
      return -1;

   // carbon has the highest priority
   if (ec2.elem == ELEM_C)
      return 1;
   if (ec1.elem == ELEM_C)
      return -1;

   // hydrogen has the highest priority after carbon
   if (ec2.elem == ELEM_H)
      return 1;
   if (ec1.elem == ELEM_H)
      return -1;

   // other elements are compared lexicographically
   return strcmp(Element::toString(ec1.elem), Element::toString(ec2.elem));
}

void GrossFormula::collect (const Molecule &molecule, Array<int> &gross, bool is_query)
{
   int i;

   gross.clear_resize(ELEM_MAX);
   gross.zerofill();

   for (i = molecule.vertexBegin(); i < molecule.vertexEnd(); i = molecule.vertexNext(i))
   {
      if (is_query && ((const Molecule &)molecule).getQueryAtom(i).type != 0)
         continue;

      int elem = molecule.getAtom(i).label;

      gross[elem]++;

      if (!is_query)
         gross[ELEM_H] += molecule.getAtom(i).implicit_h;
   }
}

void GrossFormula::toString (const Array<int> &gross, Array<char> &str)
{
   _toString(gross, str, _cmp);
}

void GrossFormula::toString_Hill (const Array<int> &gross, Array<char> &str)
{
   _toString(gross, str, _cmp_hill);
}


void GrossFormula::_toString (const Array<int> &gross, Array<char> &str,
                              int (*cmp)(const _ElemCounter &, const _ElemCounter &, const void *))
{
   QS_DEF(Array<_ElemCounter>, counters);
   int i;

   counters.clear();

   for (i = 1; i < ELEM_MAX; i++)
   {
      _ElemCounter &ec = counters.push();
 
      ec.elem = i;
      ec.counter = gross[i];
   }

   counters.qsort(cmp, 0);

   ArrayOutput output(str);

   bool first_written = false;

   for (i = 0; i < counters.size(); i++)
   {
      if (counters[i].counter < 1)
         break; 

      if (first_written)
         output.printf(" ");

      output.printf(Element::toString(counters[i].elem));
      if (counters[i].counter > 1)
         output.printf("%d", counters[i].counter);
      first_written = true;
   }
   output.writeChar(0);
}

void GrossFormula::fromString (Scanner &scanner, Array<int> &gross)
{
   gross.clear_resize(ELEM_MAX);
   gross.zerofill();

   scanner.skipSpace();
   while (!scanner.isEOF())
   {
      int elem = Element::read(scanner);
      scanner.skipSpace();
      int counter = 1;
      
      if (isdigit(scanner.lookNext()))
      {
         counter = scanner.readUnsigned();
         scanner.skipSpace();
      }

      gross[elem] += counter;
   }
}

void GrossFormula::fromString (const char *str, Array<int> &gross)
{
   BufferScanner scanner(str);

   fromString(scanner, gross);
}

bool GrossFormula::leq (const Array<int> &gross1, const Array<int> &gross2)
{
   int i;

   for (i = 1; i < ELEM_MAX; i++)
      if (gross1[i] > gross2[i])
         return false;

   return true;
}

bool GrossFormula::geq (const Array<int> &gross1, const Array<int> &gross2)
{
   int i;

   for (i = 1; i < ELEM_MAX; i++)
      if (gross2[i] > 0 && gross1[i] < gross2[i])
         return false;

   return true;
}

bool GrossFormula::equal (const Array<int> &gross1, const Array<int> &gross2)
{
   int i;

   for (i = 1; i < ELEM_MAX; i++)
      if (gross2[i] != gross1[i])
         return false;

   return true;
}
